package drds.plus.api;

import drds.plus.executor.cursor.cursor.IAffectRowCursor;
import drds.plus.executor.cursor.cursor.impl.result_cursor.ResultCursor;
import drds.plus.executor.cursor.cursor_metadata.CursorMetaData;
import drds.plus.executor.row_values.RowValues;
import drds.plus.executor.utils.Utils;
import drds.plus.sql_process.abstract_syntax_tree.configuration.ColumnMetaData;

import java.math.BigDecimal;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.*;

public class RowDataSet {

    private final ResultCursor resultCursor;
    private final Map columnToIndexCache = new HashMap();
    /**
     * Has this result setLimitValue been closed?
     */
    protected boolean isClosed = false;
    private RowValues currentRowData;
    private RowValues cacheRowDataToBuildMeta = null;
    private RowDataMetaData rowDataMetaData = null;
    private boolean wasNull;
    private boolean isLoigcalIndexEqualActualIndex;
    private Map<Integer, Integer> logicalIndexToActualIndex = null;
    private Map columnLabelToIndex;
    private Map fullColumnNameToIndex;
    private Map columnNameToIndex;
    private boolean hasBuiltIndexMapping = false;
    private boolean useColumnNamesInFindColumn = false;

    public RowDataSet(ResultCursor resultCursor) {
        this.resultCursor = resultCursor;
        if (this.resultCursor != null && this.resultCursor.getOriginalSelectColumns() != null && !this.resultCursor.getOriginalSelectColumns().isEmpty()) {
            this.rowDataMetaData = new RowDataMetaData(Utils.convertListToColumnMetaDataList(this.resultCursor.getOriginalSelectColumns()));
        }

    }

    public void buildIndexMapping() throws SQLException {
        int columnCount = this.getRowDataMetaData().getColumnCount();
        this.columnLabelToIndex = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        this.fullColumnNameToIndex = new TreeMap(String.CASE_INSENSITIVE_ORDER);
        this.columnNameToIndex = new TreeMap(String.CASE_INSENSITIVE_ORDER);


        List<ColumnMetaData> columnMetaDataList = this.getRowDataMetaData().getColumnMetaDataList();
        for (int i = columnCount - 1; i >= 0; i--) {
            Integer index = i;
            String columnName = columnMetaDataList.get(i).getColumnName();
            String columnLabel = columnMetaDataList.get(i).getAlias() == null ? columnName : columnMetaDataList.get(i).getAlias();

            String fullColumnName = columnMetaDataList.get(i).getFullName();

            if (columnLabel != null) {
                this.columnLabelToIndex.put(columnLabel, index);
            }

            if (fullColumnName != null) {
                this.fullColumnNameToIndex.put(fullColumnName, index);
            }

            if (columnName != null) {
                this.columnNameToIndex.put(columnName, index);
            }
        }

        // setLimitValue the flag to prevent rebuilding...
        this.hasBuiltIndexMapping = true;
    }

    public synchronized int findColumn(String columnName) throws SQLException {
        Integer index;
        checkClosed();

        if (!this.hasBuiltIndexMapping) {
            buildIndexMapping();
        }

        index = (Integer) this.columnToIndexCache.get(columnName);
        if (index != null) {
            return index.intValue() + 1;
        }

        index = (Integer) this.columnLabelToIndex.get(columnName);
        if (index == null && this.useColumnNamesInFindColumn) {
            index = (Integer) this.columnNameToIndex.get(columnName);
        }

        if (index == null) {
            index = (Integer) this.fullColumnNameToIndex.get(columnName);
        }

        if (index != null) {
            this.columnToIndexCache.put(columnName, index);
            return index.intValue() + 1;
        }

        // Try this inefficient way, now

        List<ColumnMetaData> columnMetaDataList = this.getRowDataMetaData().getColumnMetaDataList();
        for (int i = 0; i < columnMetaDataList.size(); i++) {
            String cn = columnMetaDataList.get(i).getAlias() == null ? columnMetaDataList.get(i).getColumnName() : columnMetaDataList.get(i).getAlias();
            if (columnName.equalsIgnoreCase(cn)) {
                return i + 1;
            } else if (columnName.equalsIgnoreCase(columnMetaDataList.get(i).getFullName())) {
                return i + 1;
            }
        }

        throw new SQLException("column " + columnName + " doesn't exist!, " + this.getRowDataMetaData().getColumnMetaDataList());
    }

    // 游标指向下一跳记录

    public boolean next() throws SQLException {
        checkClosed();
        RowValues rowData;
        try {
            if (cacheRowDataToBuildMeta != null) {
                rowData = cacheRowDataToBuildMeta;
                cacheRowDataToBuildMeta = null;
            } else {
                rowData = resultCursor.next();
            }

            this.currentRowData = rowData;
        } catch (Exception e) {
            this.currentRowData = null;
            throw new RuntimeException(e);
        }
        return null != rowData;
    }

    public int getAffectRows() throws SQLException {
        if (this.resultCursor.getCursor() instanceof IAffectRowCursor) {
            if (currentRowData != null || next()) {
                Integer index = currentRowData.getParentCursorMetaData().getIndex(null, ResultCursor.AFFECT_ROW, null);

                return currentRowData.getInteger(index);
            } else {
                return 0;
            }
        }

        return -1;

    }

    private void checkClosed() throws SQLException {
        if (this.isClosed) {
            throw new SQLException("RowDataSet.Operation_not_allowed_after_ResultSet_closed");
        }
    }

    public void close() throws SQLException {
        if (isClosed) {
            return;
        }
        try {
            this.rowDataMetaData = null;
            List<RuntimeException> exs = new ArrayList();
            exs = this.resultCursor.close(exs);
            if (!exs.isEmpty()) {
                throw exs.get(0);
            }
            isClosed = true;

        } catch (Exception e) {
            throw new RuntimeException(e);

        }
    }

    public boolean isClosed() throws SQLException {
        return this.isClosed;
    }


    private void validateColumnIndex(int columnIndex) throws SQLException {
        if (columnIndex < 0 || columnIndex > this.getRowDataMetaData().getColumnCount()) {
            throw new SQLException("columnIndex 越界，column size：" + this.getRowDataMetaData().getColumnCount());
        }
    }

    /**
     * @param logicalIndex 用户select时的index
     * @return IRowSet中实际的index
     * @throws SQLException
     */
    private int getActualIndex(int logicalIndex) {
        if (this.logicalIndexToActualIndex == null) {
            logicalIndexToActualIndex = new HashMap<Integer, Integer>();
            isLoigcalIndexEqualActualIndex = true;
            if (this.currentRowData == null) {
                return logicalIndex;
            }

            CursorMetaData cursorMetaData = currentRowData.getParentCursorMetaData();
            if (cursorMetaData.isSureLogicalIndexEqualActualIndex()) {
                // 如果确定相等，就不需要挨个去判断了
                isLoigcalIndexEqualActualIndex = true;
            } else {
                try {
                    for (int i = 0; i < this.getRowDataMetaData().getColumnCount(); i++) {
                        ColumnMetaData columnMetaData = this.getRowDataMetaData().getColumnMetaDataList().get(i);
                        @SuppressWarnings("unused")
                        String name = columnMetaData.getColumnName();
                        String tableName = columnMetaData.getTableName();
                        Integer indexInCursorMeta = null;

                        // 要以别名优先

                        indexInCursorMeta = cursorMetaData.getIndex(tableName, columnMetaData.getColumnName(), columnMetaData.getAlias());
                        if (indexInCursorMeta == null) {
                            throw new RuntimeException("impossible");
                        }
                        logicalIndexToActualIndex.put(i, indexInCursorMeta);
                        if (i != indexInCursorMeta) {
                            isLoigcalIndexEqualActualIndex = false;
                        }
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        if (isLoigcalIndexEqualActualIndex) {
            return logicalIndex;
        } else {
            Integer actualIndex = logicalIndexToActualIndex.get(logicalIndex);
            return actualIndex;
        }
    }

    public String getString(String columnLabel) throws SQLException {
        return getString(findColumn(columnLabel));
    }

    public String getString(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        String str = currentRowData.getString(getActualIndex(columnIndex));
        if (str == null) {
            wasNull = true;
            return str;
        } else {
            wasNull = false;
            return str;
        }
    }

    public boolean getBoolean(String columnLabel) throws SQLException {
        return getBoolean(findColumn(columnLabel));
    }

    public boolean getBoolean(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Boolean bool = currentRowData.getBoolean(getActualIndex(columnIndex));
        if (null == bool) {
            wasNull = true;
            return false;
        } else {
            wasNull = false;
            return bool;
        }
    }

    public short getShort(String columnLabel) throws SQLException {
        return getShort(findColumn(columnLabel));
    }

    public short getShort(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Short st = currentRowData.getShort(getActualIndex(columnIndex));
        if (st == null) {
            wasNull = true;
            return 0;
        } else {
            wasNull = false;
            return st;
        }
    }

    public int getInt(String columnLabel) throws SQLException {
        return getInt(findColumn(columnLabel));
    }

    public int getInt(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Integer inte = currentRowData.getInteger(getActualIndex(columnIndex));
        if (inte == null) {
            wasNull = true;
            return 0;
        } else {
            wasNull = false;
            return inte;
        }
    }

    public long getLong(String columnLabel) throws SQLException {
        return getLong(findColumn(columnLabel));
    }

    public long getLong(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Long l = currentRowData.getLong(getActualIndex(columnIndex));
        if (l == null) {
            wasNull = true;
            return 0;
        } else {
            wasNull = false;
            return l;
        }
    }

    public float getFloat(String columnLabel) throws SQLException {
        return getFloat(findColumn(columnLabel));

    }

    public float getFloat(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Float fl = currentRowData.getFloat(getActualIndex(columnIndex));
        if (fl == null) {
            wasNull = true;
            return 0;
        } else {
            wasNull = false;
            return fl;
        }
    }

    public double getDouble(String columnLabel) throws SQLException {
        return getDouble(findColumn(columnLabel));
    }

    public double getDouble(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Double doub = currentRowData.getDouble(getActualIndex(columnIndex));
        if (doub == null) {
            wasNull = true;
            return 0;
        } else {
            wasNull = false;
            return doub;
        }
    }


    public Date getDate(String columnLabel) throws SQLException {
        return getDate(findColumn(columnLabel));
    }

    public Date getDate(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Date date = currentRowData.getDate(getActualIndex(columnIndex));
        if (date == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            return date;
        }
    }


    public Timestamp getTimestamp(String columnLabel) throws SQLException {
        return getTimestamp(findColumn(columnLabel));
    }

    public Timestamp getTimestamp(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Timestamp ts = currentRowData.getTimestamp(getActualIndex(columnIndex));
        if (ts == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            return ts;
        }
    }

    public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
        Timestamp ts = getTimestamp(columnIndex);
        if (ts == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            cal.setTimeInMillis(ts.getTime());
            return new Timestamp(cal.getTimeInMillis());
        }
    }

    public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
        Timestamp ts = getTimestamp(columnLabel);
        if (ts == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            cal.setTimeInMillis(ts.getTime());
            return new Timestamp(cal.getTimeInMillis());
        }
    }

    public Time getTime(String columnLabel) throws SQLException {
        Timestamp ts = getTimestamp(columnLabel);
        if (ts == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            return new Time(ts.getTime());
        }
    }

    public Time getTime(int columnIndex) throws SQLException {
        Timestamp ts = getTimestamp(columnIndex);
        if (ts == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            return new Time(ts.getTime());
        }
    }

    public Time getTime(int columnIndex, Calendar cal) throws SQLException {
        Timestamp ts = getTimestamp(columnIndex);
        if (ts == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            cal.setTimeInMillis(ts.getTime());
            return new Time(cal.getTimeInMillis());
        }
    }

    public Time getTime(String columnLabel, Calendar cal) throws SQLException {
        Timestamp ts = getTimestamp(columnLabel);
        if (ts == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            cal.setTimeInMillis(ts.getTime());
            return new Time(cal.getTimeInMillis());
        }
    }

    public Object getObject(String columnLabel) throws SQLException {
        return getObject(findColumn(columnLabel));
    }

    public Object getObject(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        Object obj = currentRowData.getObject(getActualIndex(columnIndex));
        if (obj == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            return obj;
        }
    }

    public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
        return getBigDecimal(findColumn(columnLabel));
    }

    public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
        return getBigDecimal(findColumn(columnLabel), scale);
    }

    public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
        BigDecimal decimal = getBigDecimal(columnIndex);
        if (decimal != null) {
            try {
                return decimal.setScale(scale);
            } catch (ArithmeticException ex) {
                try {
                    return decimal.setScale(scale, BigDecimal.ROUND_HALF_UP);
                } catch (ArithmeticException arEx) {
                    throw new RuntimeException(arEx);
                }
            }
        }

        return decimal;
    }

    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
        validateColumnIndex(columnIndex);
        columnIndex--;
        BigDecimal value = currentRowData.getBigDecimal(getActualIndex(columnIndex));
        if (value == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            return value;
        }

    }

    public Date getDate(int columnIndex, Calendar cal) throws SQLException {
        Date date = getDate(columnIndex);
        if (date == null) {
            wasNull = true;
            return null;
        } else {
            wasNull = false;
            cal.setTimeInMillis(date.getTime());
            return new Date(cal.getTimeInMillis());
        }
    }

    public Date getDate(String columnLabel, Calendar cal) throws SQLException {
        return getDate(findColumn(columnLabel), cal);
    }

    public boolean wasNull() throws SQLException {
        return wasNull;
    }

    public RowDataMetaData getRowDataMetaData() throws SQLException {
        checkClosed();
        if (this.rowDataMetaData != null) {
            return this.rowDataMetaData;
        }
        RowValues rowData = null;
        if (this.currentRowData != null) {
            rowData = currentRowData;
        } else {
            if (this.cacheRowDataToBuildMeta == null) {
                try {
                    cacheRowDataToBuildMeta = resultCursor.next();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            rowData = cacheRowDataToBuildMeta;
        }
        if (rowData == null) {
            rowDataMetaData = new RowDataMetaData(resultCursor.getColumnMetaDataList());
        } else {
            rowDataMetaData = new RowDataMetaData(rowData.getParentCursorMetaData().getColumnMetaDataList());
        }
        return rowDataMetaData;
    }


}
